package com.gitee.melin.bee.core.jdbc.relational;

import java.util.NoSuchElementException;
import java.util.StringTokenizer;

public class DatabaseVersion {
    /** Constant used to indicate that no version is defined */
    private static final int NO_VERSION = -9999;

    private static final char SEPARATOR = '.';

    private static final String SEPARATOR_STRING = ".";

    private final int major;

    private final int minor;

    private final int micro;

    private transient String versionString;

    public DatabaseVersion(int major, int minor, int micro) {
        this.major = major;
        this.minor = minor;
        this.micro = micro;
        validate();
    }

    public DatabaseVersion(String version) {
        int maj = 0;
        int min = 0;
        int mic = 0;

        try {
            StringTokenizer st = new StringTokenizer(version, SEPARATOR_STRING, true);
            maj = parseInt(st.nextToken(), version);

            if (st.hasMoreTokens()) { // minor
                st.nextToken(); // consume delimiter
                min = parseInt(st.nextToken(), version);

                if (st.hasMoreTokens()) { // micro
                    st.nextToken(); // consume delimiter
                    mic = parseInt(st.nextToken(), version);

                    if (st.hasMoreTokens()) { // qualifier separator
                        st.nextToken(); // consume delimiter

                        if (st.hasMoreTokens()) { // fail safe
                            throw new IllegalArgumentException("invalid version \"" + version + "\": invalid format");
                        }
                    }
                }
            }
        } catch (NoSuchElementException e) {
            throw new IllegalArgumentException("invalid version \"" + version + "\": invalid format", e);
        }

        major = maj;
        minor = min;
        micro = mic;
        validate();
    }

    private int parseInt(String value, String version) {
        try {
            return Integer.parseInt(value);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("invalid version \"" + version + "\": non-numeric \"" + value + "\"", e);
        }
    }

    private void validate() {
        if (major < 0) {
            throw new IllegalArgumentException(
                    "invalid version \"" + toString0() + "\": negative number \"" + major + "\"");
        }
        if (minor < 0) {
            throw new IllegalArgumentException(
                    "invalid version \"" + toString0() + "\": negative number \"" + minor + "\"");
        }
        if (micro < 0) {
            throw new IllegalArgumentException(
                    "invalid version \"" + toString0() + "\": negative number \"" + micro + "\"");
        }
    }

    private String toString0() {
        String s = versionString;
        if (s != null) {
            return s;
        }

        String result = String.valueOf(major) + SEPARATOR + minor + SEPARATOR + micro;
        return versionString = result;
    }

    public int getDatabaseMajorVersion() {
        return major;
    }

    public int getDatabaseMinorVersion() {
        return minor;
    }

    public int getDatabaseMicroVersion() {
        return micro;
    }

    public int getMajor() {
        return major;
    }

    public int getMinor() {
        return minor;
    }

    public int getMicro() {
        return micro;
    }

    /** Determine if the versions are the same/equal. */
    public boolean isSame(DatabaseVersion other) {
        return isSame(
                other.getDatabaseMajorVersion(), other.getDatabaseMinorVersion(), other.getDatabaseMicroVersion());
    }

    /** Determine if this version matches the passed one. */
    public boolean isSame(int otherMajor) {
        return getDatabaseMajorVersion() == otherMajor;
    }

    /** Determine if this version matches the passed one. */
    public boolean isSame(int otherMajor, int otherMinor) {
        return isSame(otherMajor) && getDatabaseMinorVersion() == otherMinor;
    }

    /** Determine if this version matches the passed one. */
    public boolean isSame(int otherMajor, int otherMinor, int otherMicro) {
        return isSame(otherMajor, otherMinor) && getDatabaseMicroVersion() == otherMicro;
    }

    /** {@link #isSame} or {@link #isAfter} */
    public boolean isSameOrAfter(DatabaseVersion other) {
        return isSameOrAfter(other.getDatabaseMajorVersion(), other.getDatabaseMinorVersion());
    }

    /** {@link #isSame} or {@link #isAfter} */
    public boolean isSameOrAfter(Integer otherMajor, Integer otherMinor) {
        return isSameOrAfter((int) otherMajor, otherMinor == null ? NO_VERSION : otherMinor);
    }

    /** {@link #isSame} or {@link #isAfter} */
    public boolean isSameOrAfter(int otherMajor) {
        final int major = getDatabaseMajorVersion();

        return major >= otherMajor;
    }

    /** {@link #isSame} or {@link #isAfter} */
    public boolean isSameOrAfter(int otherMajor, int otherMinor) {
        final int major = getDatabaseMajorVersion();
        final int minor = getDatabaseMinorVersion();

        return major > otherMajor || (major == otherMajor && minor >= otherMinor);
    }

    /** {@link #isSame} or {@link #isAfter} */
    public boolean isSameOrAfter(int otherMajor, int otherMinor, int otherMicro) {
        final int major = getDatabaseMajorVersion();
        final int minor = getDatabaseMinorVersion();
        final int micro = getDatabaseMicroVersion();

        return major > otherMajor
                || (major == otherMajor && minor > otherMinor)
                || (major == otherMajor && minor == otherMinor && micro >= otherMicro);
    }

    /** Determine whether this version comes after the passed one */
    public boolean isAfter(DatabaseVersion other) {
        return isAfter(other.getDatabaseMajorVersion(), other.getDatabaseMinorVersion());
    }

    /** Determine whether this version after the passed one */
    public boolean isAfter(Integer major, Integer minor) {
        return isAfter((int) major, minor == null ? NO_VERSION : minor);
    }

    /** Determine whether this version after the passed one */
    public boolean isAfter(int major) {
        return getDatabaseMajorVersion() > major;
    }

    /** Determine whether this version after the passed one */
    public boolean isAfter(int major, int minor) {
        return getDatabaseMajorVersion() > major
                || (getDatabaseMajorVersion() == major && getDatabaseMinorVersion() > minor);
    }

    /** Determine whether this version after the passed one */
    public boolean isAfter(int otherMajor, int otherMinor, int otherMicro) {
        final int major = getDatabaseMajorVersion();
        final int minor = getDatabaseMinorVersion();
        final int micro = getDatabaseMicroVersion();

        return major > otherMajor
                || (major == otherMajor && minor > otherMinor)
                || (major == otherMajor && minor == otherMinor && otherMicro > micro);
    }

    /** Determine whether this version comes before the passed one */
    public boolean isBefore(DatabaseVersion other) {
        return isBefore(other.getDatabaseMajorVersion(), other.getDatabaseMinorVersion());
    }

    /** Determine whether this version before the passed one */
    public boolean isBefore(int major, int minor) {
        return !isSameOrAfter(major, minor);
    }

    /** Determine whether this version before the passed one */
    public boolean isBefore(int major) {
        return !isSameOrAfter(major);
    }

    /** Determine whether this version before the passed one */
    public boolean isBefore(Integer major, Integer minor) {
        return isBefore((int) major, minor == null ? NO_VERSION : minor);
    }

    /** Determine whether this version before the passed one */
    public boolean isBefore(int otherMajor, int otherMinor, int otherMicro) {
        return !isSameOrAfter(otherMajor, otherMinor, otherMicro);
    }
}
